The Assembly Advantage by Randall Hyde
- APRIL, MAY, JUNE, JULY, SEPTEMBER, OCTOBER, NOVEMBER, DECEMBER 1983 inCider.

Trying Out the Tools — Some Basics
Once the beginner masters his Apple computer and develops a strong command of
the Basic language, thoughts immediately turn to optimization.

There’s probably not a single program written that couldn’t benefit from extra speed
or additional features. So the beginner learns Basic tricks, like removing REMS,
moving subroutines to the beginning of the program, and declaring often - used variables
early in the program.

Ultimately, however, jerry-rigging fails to achieve the needed improvement
and the programmer is forced to contemplate use of a faster language.
While faster high-level languages, like Pascal and Forth, are available for the Apple II,
these languages can’t come close to 6502 assembly language.

For the programmer writing time-critical code (like a hi-res arcade game)
using 6502 machine code is an absolute necessity.

However, 6502 assembly language, the gateway to 6502 machine code, is difficult to learn.

To ease the burden I have created a set of subroutines, collectively called Speed/Asm,
to help minimize the effort.

This series of articles will describe how you can easily create your own machine language
programs using the Speed/Asm package.

Speed/Asm, like. its companion, the LISA interactive assembler,
is especially designed for the beginner at 6502 assembly language programming.

I developed these packages as tools for use in the assembly language classes I teach
in southern California. I've discovered that students who learn 6502 assembly language
using the Speed/ Asm package achieve competence much faster than those who learn using
traditional methods of instruction.

Speed/Asm is a collection of subroutines you call from your program to perform certain tasks.

In particular, the Speed/Asm subroutines emulate many of the statements found in Basic
and Pascal.

For example, the Basic program

      10 FOR I = 1 TO 100
      20 PRINT I;
      30 NEXT I

is translated into Speed/Asm as

      JSR FOR
      ADR I,1,100
      JSR PRTINT
      ADR I
      JSR NEXT

Persons using a macro assembler, like LISA version 3.0, could even code this as;

      .FOR I,1,100
      .PRTINT I
      .NEXT

As you can see, Speed/Asm looks quite a bit like Basic. But keep in mind that Speed/Asm
is assembly language so you get a considerable performance boost compared to Basic.

Getting Started
Probably the best place to start is with the disk that comes in the Speed/Asm package.
A quick catalog of the disk reveals that about 11 programs are included.

The file Speed/Asm is the boot program that does the cataloging.

RELSA and RELFP are two programs used to generate a Speed/Asm program.
Speed/Asm is relocatable to any page boundary; RELSA and RELFP are the programs
that create an absolute version of the Speed/Asm program for actual use.

RELFP generates a full Speed/Asm package, including the floating point operations.
RELSA generates a copy of Speed/Asm without the floating point subroutines, hence it is
much shorter.

SAFP.78, the next program on the disk, is a copy of Speed/Asm that has been located to
address $7800. This file is provided for the convenience of those who want to write
programs as quickly as possible without having to learn how to use the RELSA or RELFP
program first. I will use SAFP.78 in all the examples I present, although only one line
in your program will need to be changed if you wish to use Speed/Asm located at some other
address.

The SA.EQUATES file contains equates for all the Speed/Asm subroutines.

The file on this disk is provided in a LISA v2.5 compatible format
(which can also be read in by LISA version 3.0).

For those who are using an assembler other than the LISA interactive assembler,
the SA-EQUATES file is reproduced in the - program listing.

Incidentally, if you’re a beginner and in the process of choosing an assembler,
I would highly recommend a look at LISA. It is an interactive assembler that makes
learning assembly language much easier.

Unlike other assemblers, LISA catches errors on input, much like Basic.

I’m going to make the assumption, in this column, that you’re using the LISA assembler.

Attempting to describe every LISA feature to users of other assemblers would be too great
a distraction.

Attempting to write the code in a general fashion would make the programs overly large
and confusing.

The remaining six programs provided on the Speed/Asm disk are test programs that were
used to help validate the Speed/Asm package.

You may want to look at these files to get an idea.of how Speed/Asm programs are written.

Preparing for Your First Program
Before we can jump in and run our first program there are several decisions that must be
made. 

To begin with, we must decide where the program will run.
Unlike Basic, which uses the same data in memory for the source code and run-time code,
assembly language programs must be converted from a source (human-readable) format to an
object code (computer-readable) format.

This conversion is accomplished by an assembler like LISA.

Once the source file is converted to object code you can run the program by executing the
object code.

Most assemblers on the market (including LISA) operate in a co-resident mode.

This means that the source text file and object code both reside in memory during the
assembly of the program. Since both files are maintained in RAM at the same time,
care must be taken to ensure that the source and run-time object code do not disturb one
another.

In Basic, memory was allocated automatically for you.

While using 6502 assembly language, the memory management chore is left up to the programmer.
So extra care must be taken when creating programs.

By referring to Figure 1 you can get an idea of what the Apple’s memory space looks like
while using LISA.

In particular, locations $800 through $17FF are reserved for holding your object code,
and locations $1800 and up are reserved for the source file.

Four kilobytes of RAM should prove to be sufficient for most programs.

Advanced programmers who require more RAM should consult the LISA documentation to find
out how to adjust the default text-file/object code size settings.

Whenever you assemble a program, LISA automatically begins assembling and storing the code
at location $800.

Unless you want to assemble the code at some other location, no special action is
necessary to assemble your code into the proper space in RAM.


                      -------------------------------------
                      |                                   |
           $0000 ---> |       Apple System Variables      |
                      |                                   |
                      |-----------------------------------|
                      |                                   |
           $0400 ---> |       Apple Text Screen Memory    |
                      |                                   |
                      |-----------------------------------|
                      |                                   |
           $0800 ---> | 4K Space Reserved for User Object |
                      | Code                              |
                      |                                   |
                      |-----------------------------------|
                      |                                   |
           $1B00 ---> |     Lisa Text File Begins Here    |
                      |                                   |
                      |-----------------------------------|
                      |                                   |
           $7800 ---> |          SAFP.78 Loads Here       |
                      |                                   |
                      |-----------------------------------|
                      |                                   |
           $9600 ---> |          DOS Starts Here          |
                      |                                   |
                      |-----------------------------------|
                      |                                   |
           $C000 ---> |              I/O Space            |
                      |                                   |
                      |-----------------------------------|
                      |                                   |
           $D000 ---> |       Lisa Code Resides Here      |
                      |                                   |
                      |-----------------------------------|
                      |                                   |
           $F800 ---> |            Apple Monitor          |
                      |                                   |
                      |-----------------------------------|

              Figure 1. Apple Memory Map

Writing Your First Assembly Language Program
The first program we'll write will do nothing at all!   Actually it does do something:
it immediately returns control to the Apple monitor.

This program, as simple as it is, is important because it demonstrates how to terminate
a 6502 assembly language program.

Consult the documentation that came with your assembler and learn how to use the editor
to enter text into the source file buffer.

Once you've mastered the editor, enter the following program into the text buffer:

      EXIT EQU $FF59
      JMP EXIT
      END

Once you’ve entered this program, assemble it using the assembler
(the documentation should describe how to assemble a file).

With LISA this is accomplished by typing ASM followed by a carriage return.

Once the assembler has completed the task of assembling the code, you should access the
Apple monitor program (LISA users type BREAK) and issue the Apple monitor command 800G.

The computer will beep and return you to the monitor mode.

         Congratulations!

You've just run your first 6502 assembly language program.

A discussion of exactly what happened may help clear up any problems you have
understanding the operation of this program.

First of all, the statement

      EXIT EQU $FF59

isn’t a true 6502 assembly language statement at all.

The EQU instruction is an example of a pseudo opcode, or assembler directive,
provided by most assemblers.

This instruction tells the assembler to replace every occurrence of EXIT with the value
$FF59 during the assembly of the program.

So, the next instruction (JMP EXIT) is converted to JMP $FF59.

The JMP (jump) instruction on the second line performs the same operation as a Goto in Basic.

The JMP EXIT instruction directs the 6502 to jump to address $FF59 and begin executing
code there.

The entry point for the Apple monitor program just happens to be at address $FF59,
so jumping to this location reactivates the Apple monitor. Since the program is executed
from the Apple monitor and immediately returns control to the monitor,
it will appear that nothing has happened.

The END pseudo opcode is required at the end of all LISA source files.

It marks the physical end of the program.

The END statement does not terminate the execution of your program. It is simply a marker
for the assembler so it knows when to stop assembling the file.

To terminate the program, jump to location $FF59.

Other Methods of Terminating Your Programs
In addition to jumping to location $FF59, there are three other ways to terminate an
assembly language program.

Providing you are not within some nested subroutine and you haven’t pushed any data onto
the stack (advanced stuff), you can return control to the Apple’s monitor with
the simple command,

      RTS

This is a return - from - subroutine instruction.

It’s quite similar to Return in Basic.

This program can be rewritten as:

      RTS
      END

If you assemble and run this program, control will once again be returned to the Apple
monitor. This time, however, the speaker won't beep at you.

Another method for program termination is the BRK instruction.

This instruction (used in a manner identical to RTS) beeps the speaker, prints the
contents of the 6502’s registers, and then transfers control to the Apple monitor.

BRK is used mainly for debugging purposes, but you can use it anytime it’s convenient
to get a printout of the 6502 registers.

The last method for program termination I'll mention is very similar to the first example.

A jump to location $FF69 also transfers control to the Apple monitor.

Basic users are already familiar with this entry point to the Apple monitor.

It’s the CALL-151 instruction you use to get into the monitor in the first place.

Writing Your First Speed/Asm Program
Now that you can stop your program, the next step in learning how to use Speed/Asm
is to begin writing programs that use it.

Load the SA.EQUATES text - file into LISA and insert the following code just before
the END statement in line 121:

EXIT  EQU $FF69
      JSR INIT
      JMP EXIT

Assemble the program as before.

But before you run it (and while you're still in LISA) type control-D BLOAD SAFP,78.

Now break to the monitor and run the program by typing 800G.

Note that the program will ask if your Apple can display upper - and lowercase.

Once you type Y or N, control returns to the Apple monitor.

If you answer Y, then all text sent to the screen is sent completely unmodified.

If you answer N, then all lowercase characters are converted to uppercase before
being output to the screen.

This allows you to take advantage of lowercase add-on equipment,
like the Lazer Microsystems LowerCase+Plus and Keyboard+Plus units, without having to
worry about compatibility problems with Apples not so equipped.

The JSR INIT instruction is new.

JSR (which stands for jump to subroutine) is quite similar to GOSUB in Basic.

Control is transferred to the specified address, where some routine is executed.
The program then resumes execution at the next instruction after the JSR, whenever an RTS
instruction is executed.

The INIT subroutine entry address is defined in the Speed/Asm equates.
Init must be called before running any Speed/Asm program.
   (Failure to do so will cause the package to malfunction.)

Once INIT is through doing its thing (asking you about lowercase and initializing the system),
control is returned to the Apple monitor by the execution of the JMP EXIT instruction.

At this point we’ve mastered all the mechanics of running a Speed/Asm program:

begin by JSRing to INIT, and end with a jump to location $FF69,

Your Speed/Asm program fits in between these two instructions.

Consider the following program:

      EXIT EQU $FF69

      ;
      ;
            JSR INIT
            JSR PRINT
            BYT “This is a test”,CR,0
            JMP EXIT

It prints

   This is a test

followed by a carriage return onto the video screen.

This is but a short example of how to write your own Speed/Asm program.
I will describe what this program does when I talk about the Print subroutine.

Declaring Variables in Speed/Asm:  A Short Course in Data Structures
Speed/Asm programs can use any of four different data types:

individual character, character string, integer number, and real (floating point) number.

Unlike Basic, but similar to Pascal, storage space for all variables used in a Speed/Asm
program must be allocated somewhere in the program.

In order to define a variable for use by Speed/Asm you must understand the underlying
representations for the character, string, integer, and real data types.

The Apple’s memory space consists of 65,535 memory cells called bytes.

Each byte holds one character, or other sub-unit of data.
String, integer, and floating point variables are created by combining several bytes.

For example, integer variables reside in two contiguous bytes of RAM;
Real variables (in Speed/Asm) require eight contiguous bytes.
String variables need n+2 bytes where n is the maximum number of characters you want
the string to hold.

You must explicitly reserve sufficient space for each variable you plan to use.

Most assemblers provide several pseudo opcodes to be used to reserve blocks of memory
for multi-byte data types.

Applicable pseudo opcodes provided in the LISA assembler include;

BYT, HBY, HEX, ADR, DBY, STR, ASC, INV, BLK, .DA, DCI and DFS.

Most of them differ in how they allow you to initialize the variables you are defining.
For our purposes the BYT, ADR, STR and DFS pseudo opcodes will be used to declare variables.

  BIT     15   14   13   12   11   10    9    8    7    6    5    4    3    2    1    0
NUMBER  ---------------------------------------------------------------------------------
        |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |
        |SIGN|    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |
        |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |    |
        ---------------------------------------------------------------------------------
              \____________________________________  ___________________________________/
                                                   \/
                                           INTEGER PORTION
                          Figure 2. Two's Complement Representation

BYT reserves one byte of storage for each operand present on the line after the BYT
instruction,

For example,

the instruction,

      BYT 0,1,2,3,4

reserves five bytes of memory and initializes them to the values zero, one, two,
three and four.

Since a single byte can only represent numeric values in the range 0 to 255, any attempt
to define an initial value outside this range will be futile.

If you attempt to define a value greater than 255 using the BYT pseudo opcode,
LISA uses the low order (L.O.) byte of the value as the initial value for that location.

For example, the statement

      BYT EXIT

where EXIT is equated to $FF69 produces the same code as

      BYT $69

The high order (H.O.) of EXIT’s value is ignored by the BYT pseudo opcode.

To define a single-byte variable (possibly to hold a character value), place the
variable’s name in column 1 of the line containing the BYT pseudo opcode.

If you want to define a single-byte variable called CHAR, do so using the statement

      CHAR BYT 0

This statement defines CHAR, reserves space for it and initializes the variable to zero.

Another way to reserve space for a variable is with the DFS (define storage) pseudo opcode.

Whereas BYT’s operand specifies the initial value the space reserved occupies,

DFS’s operand specifies how many bytes are to be reserved.

If you don’t need to initialize CHAR to zero, an alternate method of defining it could be

      CHAR DFS 1

This instruction tells LISA to reserve one byte of storage for the CHAR variable.

Integer variables in Speed/Asm are represented using the standard two-byte two’s complement
format (see Figure 2).

Fifteen bits are used to hold the numeric value, and bit number 16 holds the sign.

A two-byte integer variable can represent values in the range —32768 to +32767.

Basic programmers should quickly recognize this range as the same supported by Basic
integer variables. Basic uses this same format to store integer variables.

To reserve space for an integer variable in Speed/Asm you must reserve at least two bytes.
One way to accomplish this is to follow the BYT pseudo opcode with two operands — that is,

      INTGR BYT 0,0

But this method is in-elegant because BYT should be used for declaring byte variables.

The ADR pseudo opcode provides a much better alternative to the BYT pseudo opcode for
declaring integer variables.

The previous declaration could be rewritten as 

      INTGR ADR 0

Since two bytes are reserved for each operand, only one operand need follow the ADR
pseudo opcode. Furthermore, if you follow the ADR pseudo opcode with a value that requires
two bytes to hold (like EXIT, or some value greater than 255 or less than zero),
ADR will properly store the two’s complement representation in memory for you.

Another way to reserve space for an integer variable is with the DFS statement.

The instruction

      INTGR DFS 2

reserves two bytes of storage for the integer variable INTGR.

    BYTE              7            6      5     4      3      2      1      0
   NUMBER     -------------------------------------------------------------------
              |    |      |    |      |      |      |      |      |      |      |
              |SIGN|      |SIGN|      |      |      |      |      |      |      |
              |    |      |    |      |      |      |      |      |      |      |
              -------------------------------------------------------------------
              \_____  ____/\___________________________  _______________________/
                    \/                                 \/
                Exponent                            Mantissa
                      Figure 3. Floating Point Representation

Floating point variables in Speed/Asm require eight bytes.

The only practical way to reserve space for a floating point variable is to use
the DFS statement.

Since eight bytes are required, the definition of a floating point variable takes the form;

      FLTPT DFS 8

Since LISA doesn’t support a floating point pseudo opcode, you cannot initialize a
floating point variable unless you know the hexadecimal representation for the value
you're interested in.

Figure 3 shows the floating point format used by the Speed/Asm package.

The last data type supported by the Speed/Asm package is the string data type.

Strings differ from data of other types in that they vary in length.

The amount of space you reserve for a string depends entirely on how big you wish to
allow it to grow.

A string in Speed/Asm supports two length attributes:

a maximum string size and a dynamic (current length) size.

  BYTE        0      1     2      3             m-i    m    m+i                    n+i
NUMBER   --------------------------------/---------------------------/------------------
         |Maximum|First|Second|Third|   /   |      |      |      |  /   |      |       |
         |String |Char |Char  |Char |   |   |      |      |      |  |...|      |       |
         |Length |     |      |     |   |   |      |      |      |  |   |      |       |
         |       |     |      |     |   /   |      |      |      |  /   |      |       |
         ------------------------------/---------------------------/--------------------

Notes:
1) Strings always begin with a byte containing the maximum allowable length of the string.
2) The Maximum length is followed by m characters where m is the current dynamic length
   of the string.
3) All strings are terminated with a zero byte that marks the end of the string.
4) At least n + 2 bytes must be reserved for a string with maximum length n.

        Figure 4. String representation.

Figure 4 shows the format of a string in Speed/Asm.

To declare a string you must reserve n + 2 bytes, where n is the maximum number of
characters you wish to allow the string to hold.

The first byte of the string must be initialized with the maximum length of the string.

For an uninitialized string, the second byte should contain zero.

The easiest way to define a string is to use the ADR and DFS pseudo opcodes in conjunction
with one another as follows:

      STRING ADR 40
             DFS 40

The ADR pseudo opcode initializes the first byte of the string to 40
(the maximum length of the string) and the second byte of the string is initialized to zero.

The DFS 40 instruction reserves the 40 bytes necessary to hold the string.

Additional methods of reserving space for strings will be considered as the need arises.

Placement of Variables In Your Program
One last detail concerning the declaration of variables is the placement of variables
within your Speed/Asm program.

The safest place is between the final JMP EXIT instruction and the END pseudo opcode.

For technical reasons it is imperative that you do not place your variable declarations
at the beginning or in the middle of the program.

In this article, the first instalment in a series,
I’ve discussed some features of the Speed/Asm package and how to reserve space for variables.

Next time I'll describe how to use these variables and write some real Speed/Asm programs.

